package com.jfirer.validator.descriptor;

import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.ParameterNameProvider;
import javax.validation.ValidationException;
import javax.validation.metadata.ConstraintDescriptor;
import javax.validation.metadata.CrossParameterDescriptor;
import javax.validation.metadata.ExecutableDescriptor;
import javax.validation.metadata.ParameterDescriptor;
import javax.validation.metadata.ReturnValueDescriptor;
import com.jfirer.validator.util.Commons;
import com.jfirer.validator.util.ConstraintFinderImpl;
import com.jfirer.baseutil.StringUtil;

public class ExecutableDescriptorImpl extends ElementDescriptorImpl implements ExecutableDescriptor
{
	protected String					name;
	protected boolean					hasConstrainedReturnValue;
	protected boolean					hasConstrainedParameters;
	protected List<ParameterDescriptor>	parameterDescriptors;
	protected CrossParameterDescriptor	crossParameterDescriptor;
	protected ReturnValueDescriptor		returnValueDescriptor;
	
	protected void initializeExecutableDescriptorImpl(ConstraintValidatorFactory constraintValidatorFactory, AccessibleObject accessibleObject, ParameterNameProvider parameterNameProvider)
	{
		Annotation[] annotations = accessibleObject.getAnnotations();
		Set<ConstraintDescriptor<?>> crossParameterConstraintDescriptors = new HashSet<ConstraintDescriptor<?>>();
		Set<ConstraintDescriptor<?>> returnValueConstraintDescriptors = new HashSet<ConstraintDescriptor<?>>();
		for (Annotation annotation : annotations)
		{
			if (Commons.isConstraintAnnotation(annotation))
			{
				dispatchConstraintAnnotation(constraintValidatorFactory, accessibleObject, crossParameterConstraintDescriptors, returnValueConstraintDescriptors, annotation);
			}
			else if (Commons.isMutliValueConstraintAnnotation(annotation))
			{
				for (Annotation each : Commons.getMutliValueConstraintAnnotation(annotation))
				{
					dispatchConstraintAnnotation(constraintValidatorFactory, accessibleObject, crossParameterConstraintDescriptors, returnValueConstraintDescriptors, each);
				}
			}
		}
		name = defineName(accessibleObject);
		crossParameterDescriptor = new CrossParameterDescriptorImpl(accessibleObject, crossParameterConstraintDescriptors);
		returnValueDescriptor = new ReturnValueDescriptorImpl(accessibleObject, returnValueConstraintDescriptors);
		parameterDescriptors = defineParameterDescriptors(constraintValidatorFactory, accessibleObject, parameterNameProvider);
		hasConstrainedReturnValue = returnValueDescriptor.isCascaded() ? true : returnValueDescriptor.hasConstraints() ? true : false;
		hasConstrainedParameters = defineHasConstrainedParameters();
	}
	
	private List<ParameterDescriptor> defineParameterDescriptors(ConstraintValidatorFactory constraintValidatorFactory, AccessibleObject accessibleObject, ParameterNameProvider parameterNameProvider)
	{
		int length = 0;
		List<ParameterDescriptor> parameterDescriptors = new ArrayList<ParameterDescriptor>();
		if (accessibleObject instanceof Method)
		{
			length = ((Method) accessibleObject).getParameterTypes().length;
		}
		else if (accessibleObject instanceof Constructor<?>)
		{
			length = ((Constructor<?>) accessibleObject).getParameterTypes().length;
		}
		for (int i = 0; i < length; i++)
		{
			parameterDescriptors.add(new ParameterDescriptorImpl(accessibleObject, i, parameterNameProvider, constraintValidatorFactory));
		}
		return parameterDescriptors;
	}
	
	private boolean defineHasConstrainedParameters()
	{
		if (crossParameterDescriptor.hasConstraints())
		{
			return true;
		}
		else
		{
			for (ParameterDescriptor parameterDescriptor : parameterDescriptors)
			{
				if (parameterDescriptor.hasConstraints() || parameterDescriptor.isCascaded())
				{
					return true;
				}
			}
			return false;
		}
	}
	
	private String defineName(AccessibleObject accessibleObject)
	{
		if (accessibleObject instanceof Method)
		{
			return ((Method) accessibleObject).getName();
		}
		else if (accessibleObject instanceof Constructor<?>)
		{
			return ((Constructor<?>) accessibleObject).getName();
		}
		throw new IllegalArgumentException();
	}
	
	private void dispatchConstraintAnnotation(ConstraintValidatorFactory constraintValidatorFactory, AccessibleObject accessibleObject, Set<ConstraintDescriptor<?>> crossParameterConstraintDescriptors, Set<ConstraintDescriptor<?>> returnValueConstraintDescriptors, Annotation annotation)
	{
		ConstraintDescriptor<?> constraintDescriptor = new ConstraintDescriptorImpl<Annotation>(annotation, constraintValidatorFactory);
		switch (constraintDescriptor.getValidationAppliesTo())
		{
			case IMPLICIT:
				if (accessibleObject instanceof Method)
				{
					Method method = (Method) accessibleObject;
					if (method.getParameterTypes().length == 0 && (method.getReturnType() != Void.class && method.getReturnType() != void.class))
					{
						returnValueConstraintDescriptors.add(constraintDescriptor);
					}
					else if (method.getParameterTypes().length > 0 && (method.getReturnType() == Void.class || method.getReturnType() == void.class))
					{
						crossParameterConstraintDescriptors.add(constraintDescriptor);
					}
					else
					{
						String message = StringUtil.format("方法{}.{}上的注解:{}没有明确的应用对象", ((Method) accessibleObject).getDeclaringClass().getName(), ((Method) accessibleObject).getName(), annotation.annotationType().getName());
						ValidationException exception = new ValidationException(message);
						throw exception;
					}
				}
				else if (accessibleObject instanceof Constructor<?>)
				{
					Constructor<?> method = (Constructor<?>) accessibleObject;
					if (method.getParameterTypes().length == 0)
					{
						String message = StringUtil.format("方法{}.{}上的注解:{}没有明确的应用对象", ((Method) accessibleObject).getDeclaringClass().getName(), ((Method) accessibleObject).getName(), annotation.annotationType().getName());
						ValidationException exception = new ValidationException(message);
						throw exception;
					}
					returnValueConstraintDescriptors.add(constraintDescriptor);
				}
				break;
			case RETURN_VALUE:
				returnValueConstraintDescriptors.add(constraintDescriptor);
				break;
			case PARAMETERS:
				crossParameterConstraintDescriptors.add(constraintDescriptor);
				break;
			default:
				break;
		}
	}
	
	// 覆盖原方法，因为原方法的调度在这个执行描述中完全无效
	@Override
	protected void initializeElementDescriptor(Class<?> elementClass, Set<ConstraintDescriptor<?>> constraintDescriptors)
	{
		this.elementClass = elementClass;
	}
	
	@Override
	public boolean hasConstraints()
	{
		return hasConstrainedParameters;
	}
	
	@Override
	public Set<ConstraintDescriptor<?>> getConstraintDescriptors()
	{
		return crossParameterDescriptor.getConstraintDescriptors();
	}
	
	@Override
	public ConstraintFinder findConstraints()
	{
		return new ConstraintFinderImpl(crossParameterDescriptor.getConstraintDescriptors());
	}
	
	@Override
	public String getName()
	{
		return name;
	}
	
	@Override
	public List<ParameterDescriptor> getParameterDescriptors()
	{
		return parameterDescriptors;
	}
	
	@Override
	public CrossParameterDescriptor getCrossParameterDescriptor()
	{
		return crossParameterDescriptor;
	}
	
	@Override
	public ReturnValueDescriptor getReturnValueDescriptor()
	{
		return returnValueDescriptor;
	}
	
	@Override
	public boolean hasConstrainedParameters()
	{
		return hasConstrainedParameters;
	}
	
	@Override
	public boolean hasConstrainedReturnValue()
	{
		return hasConstrainedReturnValue;
	}
	
}
